﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using pwestcott.Util;
using NUnit.Framework;
using System.Linq.Expressions;

namespace pwestcott.Util.Tests.Essence.Comparer
{
    [TestFixture]
    public class ComparerTests
    {
        class MyClass
        {
            public MyClass(int i, string s, DateTime d)
            {
                MyInt = i;
                MyString = s;
                MyDateTime = d;
            }

            public int MyInt { get; private set; }
            public string MyString { get; private set; }
            public DateTime MyDateTime { get; private set; }
        }

        class ManualComparer : IComparer<MyClass>
        {
            public int Compare(MyClass lhs, MyClass rhs)
            {
                if (rhs == null)
                    return -1;
                if (ReferenceEquals(lhs, rhs))
                    return 0;

                int result;
                result = lhs.MyInt.CompareTo(rhs.MyInt);
                if (result != 0)
                    return result;
                if (lhs.MyString == null)
                {
                    if (rhs.MyString != null)
                        return 1;
                }
                else
                {
                    result = lhs.MyString.CompareTo(rhs.MyString);
                    if (result != 0)
                        return result;
                }
                result = lhs.MyDateTime.CompareTo(rhs.MyDateTime);
                if (result != 0)
                    return result;

                return 0;
            }
        }

        static readonly int[] ints = { 42, int.MinValue, -1, 93, int.MaxValue };
        static readonly string[] strings = { "Mary", "Mark", "Paul", null, "James" };
        static readonly DateTime[] dates = { new DateTime(1976, 11, 23), DateTime.MaxValue, new DateTime(1973, 11, 13), new DateTime(1999, 4, 18), DateTime.MinValue };

        static MyClass _exemplar;
        static MyClass Exemplar { get { return _exemplar = _exemplar ?? new MyClass(ints[0], strings[0], dates[0]); } }

        private void Loop(Action<MyClass> getData)
        {
            for (int i = 0; i < ints.Length; ++i)
            {
                var theInt = ints[i];
                for (int j = 0; j < strings.Length; ++j)
                {
                    var theString = strings[j];
                    for (int k = 0; k < dates.Length; ++k)
                    {
                        var theDate = dates[k];

                        getData(new MyClass(theInt, theString, theDate));
                    }
                }
            }
        }

        [Test]
        public void TestCompareryByStringNames()
        {
            var manualComparer = new ManualComparer();
            var essenceComparer = Essence<MyClass>.CreateComparer("MyInt", "MyString", "MyDateTime");
            Loop(next =>
            {
                Assert.AreEqual(manualComparer.Compare(Exemplar, next), essenceComparer.Compare(Exemplar, next));
            });
        }

        [Test]
        public void TestComparerByLambdas()
        {
            var manualComparer = new ManualComparer();
            var essenceComparer = Essence<MyClass>.CreateComparer(c => c.MyInt, c => c.MyString, c => c.MyDateTime);
            Loop(next =>
            {
                Assert.AreEqual(manualComparer.Compare(Exemplar, next), essenceComparer.Compare(Exemplar, next));
            });
        }

        class Simple
        {
            public Simple(int value) { Value1 = value; }
            public Simple(int value1, int value2) { Value1 = value1; Value2 = value2; }
            public readonly int Value1;
            public readonly int Value2;
        }

        [Test]
        public void TestSimpleForward()
        {
            var items = new[] { new Simple(1), new Simple(2), new Simple(3) };

            var forwardComparer = Essence<Simple>.CreateComparer(x => x.Value1);

            Array.Sort(items, forwardComparer);

            Assert.AreEqual(1, items[0].Value1);
            Assert.AreEqual(2, items[1].Value1);
            Assert.AreEqual(3, items[2].Value1);
        }

        [Test]
        public void TestSimpleReverse()
        {
            var items = new[] { new Simple(1), new Simple(2), new Simple(3) };

            var reverseComparer = Essence<Simple>.CreateComparer(x => x.Action.ReverseNext, x => x.Member.Value1);
            Array.Sort(items, reverseComparer);

            Assert.AreEqual(3, items[0].Value1);
            Assert.AreEqual(2, items[1].Value1);
            Assert.AreEqual(1, items[2].Value1);
        }

        [Test]
        public void TestReverseReverseString()
        {
            var _11 = new Simple(1, 1);
            var _12 = new Simple(1, 2);
            var _21 = new Simple(2, 1);
            var _22 = new Simple(2, 2);

            var items = new[] { _11, _12, _21, _22 };
            var reverseComparer = Essence<Simple>.CreateComparer("-Value1", "-Value2");

            Array.Sort(items, reverseComparer);

            Assert.AreSame(_22, items[0]);
            Assert.AreSame(_21, items[1]);
            Assert.AreSame(_12, items[2]);
            Assert.AreSame(_11, items[3]);
        }

        [Test]
        public void TestReverseReverseLambda()
        {
            var _11 = new Simple(1, 1);
            var _12 = new Simple(1, 2);
            var _21 = new Simple(2, 1);
            var _22 = new Simple(2, 2);

            var items = new[] { _11, _12, _21, _22 };
            var reverseComparer = Essence<Simple>.CreateComparer(x => x.Action.ReverseNext, x => x.Member.Value1, x => x.Action.ReverseNext, x => x.Member.Value2);

            Array.Sort(items, reverseComparer);

            Assert.AreSame(_22, items[0]);
            Assert.AreSame(_21, items[1]);
            Assert.AreSame(_12, items[2]);
            Assert.AreSame(_11, items[3]);
        }

        [Test]
        public void TestReverseForwardString()
        {
            var _11 = new Simple(1, 1);
            var _12 = new Simple(1, 2);
            var _21 = new Simple(2, 1);
            var _22 = new Simple(2, 2);

            var items = new[] { _11, _12, _21, _22 };
            var reverseComparer = Essence<Simple>.CreateComparer("-Value1", "Value2");

            Array.Sort(items, reverseComparer);

            Assert.AreSame(_21, items[0]);
            Assert.AreSame(_22, items[1]);
            Assert.AreSame(_11, items[2]);
            Assert.AreSame(_12, items[3]);
        }

        [Test]
        public void TestReverseForwardLambda()
        {
            var _11 = new Simple(1, 1);
            var _12 = new Simple(1, 2);
            var _21 = new Simple(2, 1);
            var _22 = new Simple(2, 2);

            var items = new[] { _11, _12, _21, _22 };
            var reverseComparer = Essence<Simple>.CreateComparer(x => x.Action.ReverseNext, x => x.Member.Value1, x => x.Member.Value2);

            Array.Sort(items, reverseComparer);

            Assert.AreSame(_21, items[0]);
            Assert.AreSame(_22, items[1]);
            Assert.AreSame(_11, items[2]);
            Assert.AreSame(_12, items[3]);
        }

        [Test]
        public void TestReverseRemainingForSingle()
        {
            var items = new[] { new Simple(1), new Simple(2), new Simple(3) };

            var reverseComparer = Essence<Simple>.CreateComparer(x => x.Action.ReverseRemaining, x => x.Member.Value1);
            Array.Sort(items, reverseComparer);

            Assert.AreEqual(3, items[0].Value1);
            Assert.AreEqual(2, items[1].Value1);
            Assert.AreEqual(1, items[2].Value1);
        }

        [Test]
        public void TestReverseRemainingForAll()
        {
            var _11 = new Simple(1, 1);
            var _12 = new Simple(1, 2);
            var _21 = new Simple(2, 1);
            var _22 = new Simple(2, 2);

            var items = new[] { _11, _12, _21, _22 };
            var reverseComparer = Essence<Simple>.CreateComparer(x => x.Action.ReverseRemaining, x => x.Member.Value1, x => x.Member.Value2);

            Array.Sort(items, reverseComparer);

            Assert.AreSame(_22, items[0]);
            Assert.AreSame(_21, items[1]);
            Assert.AreSame(_12, items[2]);
            Assert.AreSame(_11, items[3]);
        }


        [Test]
        public void TestReverseRemainingReverseNext()
        {
            var _11 = new Simple(1, 1);
            var _12 = new Simple(1, 2);
            var _21 = new Simple(2, 1);
            var _22 = new Simple(2, 2);

            var items = new[] { _11, _12, _21, _22 };
            var reverseComparer = Essence<Simple>.CreateComparer(x => x.Action.ReverseRemaining, x => x.Member.Value1, x => x.Action.ReverseNext, x => x.Member.Value2);

            Array.Sort(items, reverseComparer);

            Assert.AreSame(_21, items[0]);
            Assert.AreSame(_22, items[1]);
            Assert.AreSame(_11, items[2]);
            Assert.AreSame(_12, items[3]);
        }
    }
}
